package com.javasea.devops.jmh;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

/**
@BenchmarkMode
用来配置 Mode 选项，可用于类或者方法上，这个注解的 value 是一个数组，可以把几种 Mode 集合在一起执行，如：@BenchmarkMode({Mode.SampleTime, Mode.AverageTime})，还可以设置为 Mode.All，即全部执行一遍。

Throughput：整体吞吐量，每秒执行了多少次调用，单位为 ops/time
AverageTime：用的平均时间，每次操作的平均时间，单位为 time/op
SampleTime：随机取样，最后输出取样结果的分布
SingleShotTime：只运行一次，往往同时把 Warmup 次数设为 0，用于测试冷启动时的性能
All：上面的所有模式都执行一次
 @State
 通过 State 可以指定一个对象的作用范围，JMH 根据 scope 来进行实例化和共享操作。@State 可以被继承使用，如果父类定义了该注解，
 子类则无需定义。由于 JMH 允许多线程同时执行测试，不同的选项含义如下：
 Scope.Benchmark：所有测试线程共享一个实例，测试有状态实例在多线程共享下的性能
 Scope.Group：同一个线程在同一个 group 里共享实例
 Scope.Thread：默认的 State，每个测试线程分配一个实例
 @Warmup
 预热所需要配置的一些基本测试参数，可用于类或者方法上。一般前几次进行程序测试的时候都会比较慢，所以要让程序进行几轮预热，
 保证测试的准确性。参数如下所示：
 iterations：预热的次数
 time：每次预热的时间
 timeUnit：时间的单位，默认秒
 batchSize：批处理大小，每次操作调用几次方法
 为什么需要预热？
 因为 JVM 的 JIT 机制的存在，如果某个函数被调用多次之后，JVM 会尝试将其编译为机器码，从而提高执行速度，
 所以为了让 benchmark 的结果更加接近真实情况就需要进行预热。

 */
@BenchmarkMode(Mode.AverageTime)
// 预热迭代, 预热测试不会作为最终的统计结果。
// 预热的目的是让 JVM 对被测代码进行足够多的优化，比如，在预热后，被测代码应该得到了充分的 JIT 编译和优化。
@Warmup(iterations = 3, time = 1)
// 测量迭代，每一次迭代都显示了当前的执行速率，即一个操作所花费的时间。参考结果的“Iteration”
@Measurement(iterations = 5, time = 5)
// 每个进程中的测试线程，可用于类或者方法上。
@Threads(4)
// 进行 fork 的次数，可用于类或者方法上。如果 fork 数是 2 的话，则 JMH 会 fork 出两个进程来进行测试。
@Fork(1)
// 所有测试线程共享一个实例，测试有状态实例在多线程共享下的性能
@State(value = Scope.Benchmark)
// 为统计结果的时间单位，可用于类或者方法注解
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class StringConnectTest {

    /**
     * 指定某项参数的多种情况，特别适合用来测试一个函数在不同的参数输入的情况下的性能，只能作用在字段上，使用该注解必须定义 @State 注解。
     */
    @Param(value = {"10", "50", "100"})
    private int length;

    @Benchmark
    public void testStringAdd(Blackhole blackhole) {
        String a = "";
        for (int i = 0; i < length; i++) {
            a += i;
        }
        // JVM 可能会认为变量 a 从来没有使用过，从而进行优化把整个方法内部代码移除掉，这就会影响测试结果。
        // JMH 提供了两种方式避免这种问题，一种是将这个变量作为方法返回值 return a，一种是通过 Blackhole 的 consume 来避免 JIT 的优化消除。
        blackhole.consume(a);
    }

    @Benchmark
    public void testStringBuilderAdd(Blackhole blackhole) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            sb.append(i);
        }
        blackhole.consume(sb.toString());
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(StringConnectTest.class.getSimpleName())
                // 结果导出到文件，可以用如下两个网站进行分析：http://deepoove.com/jmh-visual-chart/，https://jmh.morethan.io/
                .result("result.json")
                .resultFormat(ResultFormatType.JSON).build();
        new Runner(opt).run();
    }
}
